chipfoundry-cli 2.5.3__tar.gz → 2.5.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: chipfoundry-cli
3
- Version: 2.5.3
3
+ Version: 2.5.4
4
4
  Summary: CLI tool to automate ChipFoundry project submission to SFTP server
5
5
  Home-page: https://chipfoundry.io
6
6
  License: Apache-2.0
@@ -1,2 +1,2 @@
1
1
  """ChipFoundry CLI package: Automate project submission to SFTP."""
2
- __version__ = "2.5.3"
2
+ __version__ = "2.5.4"
@@ -1735,6 +1735,39 @@ def _sha256_file(path: Path) -> str:
1735
1735
  return h.hexdigest()
1736
1736
 
1737
1737
 
1738
+ def _ordered_sftp_uploads(upload_map: dict) -> list:
1739
+ """Return payload files first and project.json last as the commit marker."""
1740
+ config_path = upload_map.get(".cf/project.json")
1741
+ if not config_path:
1742
+ raise ValueError("SFTP push requires .cf/project.json")
1743
+ payloads = [
1744
+ (rel_path, local_path)
1745
+ for rel_path, local_path in upload_map.items()
1746
+ if rel_path != ".cf/project.json" and local_path
1747
+ ]
1748
+ payloads.append((".cf/project.json", config_path))
1749
+ return payloads
1750
+
1751
+
1752
+ def _prepared_wrapper_hash(project_json_path: str) -> str:
1753
+ with open(project_json_path, "r") as f:
1754
+ data = json.load(f)
1755
+ value = str((data.get("project") or {}).get("user_project_wrapper_hash") or "").strip()
1756
+ if not value:
1757
+ raise ValueError("project.json is missing project.user_project_wrapper_hash")
1758
+ return value
1759
+
1760
+
1761
+ def _assert_wrapper_hash_unchanged(wrapper_path: str, expected_hash: str) -> None:
1762
+ current_hash = _sha256_file(Path(wrapper_path))
1763
+ if current_hash != expected_hash:
1764
+ raise RuntimeError(
1765
+ "Wrapper GDS changed while it was being uploaded. "
1766
+ "project.json was not uploaded; stop any process modifying the GDS "
1767
+ "and run cf push --force-overwrite again."
1768
+ )
1769
+
1770
+
1738
1771
  def _collect_push_candidates(project_root: Path) -> List[Tuple[str, Path, int, str]]:
1739
1772
  """Return [(rel_path, abs_path, size, kind)] for files the platform
1740
1773
  accepts via --https.
@@ -2111,17 +2144,22 @@ def push(project_root, sftp_host, sftp_username, sftp_key, project_id, project_n
2111
2144
  cf_dir = ensure_cf_directory(project_root)
2112
2145
 
2113
2146
  # Find the GDS file path for hash calculation
2114
- gds_path = None
2115
- for gds_key, gds_path in collected.items():
2147
+ wrapper_path = None
2148
+ for gds_key, local_path in collected.items():
2116
2149
  if gds_key.startswith("gds/"):
2150
+ wrapper_path = local_path
2117
2151
  break
2152
+ if not wrapper_path:
2153
+ console.print("[red]No wrapper GDS found in collected project files.[/red]")
2154
+ raise click.Abort()
2118
2155
 
2119
2156
  project_json_path = update_or_create_project_json(
2120
2157
  cf_dir=str(cf_dir),
2121
- gds_path=gds_path,
2158
+ gds_path=wrapper_path,
2122
2159
  cli_overrides=cli_overrides,
2123
2160
  existing_json_path=collected.get(".cf/project.json")
2124
2161
  )
2162
+ prepared_wrapper_hash = _prepared_wrapper_hash(project_json_path)
2125
2163
 
2126
2164
  # SFTP upload or dry-run
2127
2165
  final_project_name = project_name or (
@@ -2135,16 +2173,14 @@ def push(project_root, sftp_host, sftp_username, sftp_key, project_id, project_n
2135
2173
  upload_map["verilog/rtl/user_defines.v"] = collected.get("verilog/rtl/user_defines.v")
2136
2174
 
2137
2175
  # Add the appropriate GDS file based on what was collected
2138
- for gds_key, gds_path in collected.items():
2176
+ for gds_key, local_path in collected.items():
2139
2177
  if gds_key.startswith("gds/"):
2140
- upload_map[gds_key] = gds_path
2178
+ upload_map[gds_key] = local_path
2141
2179
 
2142
2180
  if dry_run:
2143
2181
  console.print("[bold]Files to upload:[/bold]")
2144
- for rel_path, local_path in upload_map.items():
2145
- if local_path:
2146
- remote_path = os.path.join(sftp_base, rel_path)
2147
- console.print(f" {os.path.basename(local_path)} → {rel_path}")
2182
+ for rel_path, local_path in _ordered_sftp_uploads(upload_map):
2183
+ console.print(f" {os.path.basename(local_path)} → {rel_path}")
2148
2184
  return
2149
2185
 
2150
2186
  console.print(f"Connecting to {sftp_host}...")
@@ -2163,15 +2199,16 @@ def push(project_root, sftp_host, sftp_username, sftp_key, project_id, project_n
2163
2199
  raise click.Abort()
2164
2200
 
2165
2201
  try:
2166
- for rel_path, local_path in upload_map.items():
2167
- if local_path:
2168
- remote_path = os.path.join(sftp_base, rel_path)
2169
- upload_with_progress(
2170
- sftp,
2171
- local_path=local_path,
2172
- remote_path=remote_path,
2173
- force_overwrite=force_overwrite
2174
- )
2202
+ for rel_path, local_path in _ordered_sftp_uploads(upload_map):
2203
+ if rel_path == ".cf/project.json":
2204
+ _assert_wrapper_hash_unchanged(wrapper_path, prepared_wrapper_hash)
2205
+ remote_path = os.path.join(sftp_base, rel_path)
2206
+ upload_with_progress(
2207
+ sftp,
2208
+ local_path=local_path,
2209
+ remote_path=remote_path,
2210
+ force_overwrite=force_overwrite
2211
+ )
2175
2212
  console.print(f"[green]✓ Uploaded to {sftp_base}[/green]")
2176
2213
 
2177
2214
  except Exception as e:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "chipfoundry-cli"
3
- version = "2.5.3"
3
+ version = "2.5.4"
4
4
  description = "CLI tool to automate ChipFoundry project submission to SFTP server"
5
5
  authors = ["ChipFoundry <marwan.abbas@chipfoundry.io>"]
6
6
  readme = "README.md"
File without changes