diff options
Diffstat (limited to 'classes/go-mod-vcs.bbclass')
| -rw-r--r-- | classes/go-mod-vcs.bbclass | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/classes/go-mod-vcs.bbclass b/classes/go-mod-vcs.bbclass index 65211445..549fa9f5 100644 --- a/classes/go-mod-vcs.bbclass +++ b/classes/go-mod-vcs.bbclass | |||
| @@ -1138,3 +1138,227 @@ python do_sync_go_files() { | |||
| 1138 | } | 1138 | } |
| 1139 | 1139 | ||
| 1140 | addtask sync_go_files after do_create_module_cache before do_compile | 1140 | addtask sync_go_files after do_create_module_cache before do_compile |
| 1141 | |||
| 1142 | |||
| 1143 | do_fix_go_mod_permissions() { | ||
| 1144 | # Go module cache is intentionally read-only for integrity, but this breaks | ||
| 1145 | # BitBake's rm -rf cleanup (sstate_eventhandler_reachablestamps). | ||
| 1146 | # Make all files writable so workdir can be cleaned properly. | ||
| 1147 | # | ||
| 1148 | # Check multiple possible locations where Go module cache might exist | ||
| 1149 | for modpath in "${S}/pkg/mod" "${S}/src/import/pkg/mod"; do | ||
| 1150 | if [ -d "$modpath" ]; then | ||
| 1151 | chmod -R u+w "$modpath" 2>/dev/null || true | ||
| 1152 | bbnote "Fixed permissions on Go module cache: $modpath" | ||
| 1153 | fi | ||
| 1154 | done | ||
| 1155 | # Also check sources subdirectory (for recipes with WORKDIR/sources layout) | ||
| 1156 | if [ -d "${WORKDIR}/sources" ]; then | ||
| 1157 | find "${WORKDIR}/sources" -type d -name "mod" -path "*/pkg/mod" 2>/dev/null | while read modpath; do | ||
| 1158 | chmod -R u+w "$modpath" 2>/dev/null || true | ||
| 1159 | bbnote "Fixed permissions on Go module cache: $modpath" | ||
| 1160 | done | ||
| 1161 | fi | ||
| 1162 | } | ||
| 1163 | |||
| 1164 | # Run after sync_go_files (which is the last Go module setup task) and before compile | ||
| 1165 | addtask fix_go_mod_permissions after do_sync_go_files before do_compile | ||
| 1166 | |||
| 1167 | |||
| 1168 | python do_go_mod_recommend() { | ||
| 1169 | """ | ||
| 1170 | Analyze VCS-fetched modules and recommend candidates for gomod:// conversion. | ||
| 1171 | |||
| 1172 | This task delegates to oe-go-mod-fetcher-hybrid.py --recommend to avoid | ||
| 1173 | duplicating the analysis logic. The script handles: | ||
| 1174 | - Module size calculation from vcs_cache | ||
| 1175 | - Grouping by prefix (github.com/containerd, k8s.io, etc.) | ||
| 1176 | - Suggesting --git prefixes for VCS-priority modules | ||
| 1177 | - Generating conversion command lines | ||
| 1178 | |||
| 1179 | Run with: bitbake <recipe> -c go_mod_recommend | ||
| 1180 | """ | ||
| 1181 | import subprocess | ||
| 1182 | from pathlib import Path | ||
| 1183 | |||
| 1184 | # Find the hybrid script in meta-virtualization layer | ||
| 1185 | layerdir = None | ||
| 1186 | for layer in d.getVar('BBLAYERS').split(): | ||
| 1187 | if 'meta-virtualization' in layer: | ||
| 1188 | layerdir = layer | ||
| 1189 | break | ||
| 1190 | |||
| 1191 | if not layerdir: | ||
| 1192 | bb.error("Could not find meta-virtualization layer in BBLAYERS") | ||
| 1193 | return | ||
| 1194 | |||
| 1195 | scriptpath = Path(layerdir) / "scripts" / "oe-go-mod-fetcher-hybrid.py" | ||
| 1196 | if not scriptpath.exists(): | ||
| 1197 | bb.error(f"Hybrid script not found at {scriptpath}") | ||
| 1198 | return | ||
| 1199 | |||
| 1200 | # Get recipe directory and workdir | ||
| 1201 | recipedir = d.getVar('FILE_DIRNAME') | ||
| 1202 | workdir = d.getVar('WORKDIR') | ||
| 1203 | |||
| 1204 | # Build command to run the hybrid script with --recommend | ||
| 1205 | cmd = [ | ||
| 1206 | 'python3', str(scriptpath), | ||
| 1207 | '--recipedir', recipedir, | ||
| 1208 | '--recommend' | ||
| 1209 | ] | ||
| 1210 | |||
| 1211 | # Add workdir if vcs_cache exists there (for size calculations) | ||
| 1212 | vcs_cache = Path(workdir) / "sources" / "vcs_cache" | ||
| 1213 | if vcs_cache.exists(): | ||
| 1214 | cmd.extend(['--workdir', workdir]) | ||
| 1215 | |||
| 1216 | bb.note(f"Running: {' '.join(cmd)}") | ||
| 1217 | |||
| 1218 | try: | ||
| 1219 | result = subprocess.run( | ||
| 1220 | cmd, | ||
| 1221 | capture_output=True, | ||
| 1222 | text=True, | ||
| 1223 | timeout=300 # 5 minute timeout | ||
| 1224 | ) | ||
| 1225 | |||
| 1226 | # Print stdout (the recommendations) | ||
| 1227 | if result.stdout: | ||
| 1228 | for line in result.stdout.splitlines(): | ||
| 1229 | bb.plain(line) | ||
| 1230 | |||
| 1231 | # Print any errors | ||
| 1232 | if result.stderr: | ||
| 1233 | for line in result.stderr.splitlines(): | ||
| 1234 | bb.warn(line) | ||
| 1235 | |||
| 1236 | if result.returncode != 0: | ||
| 1237 | bb.warn(f"Script exited with code {result.returncode}") | ||
| 1238 | |||
| 1239 | except subprocess.TimeoutExpired: | ||
| 1240 | bb.error("Recommendation script timed out after 5 minutes") | ||
| 1241 | except Exception as e: | ||
| 1242 | bb.error(f"Failed to run recommendation script: {e}") | ||
| 1243 | |||
| 1244 | # Always print the recipe configuration hint | ||
| 1245 | bb.plain("") | ||
| 1246 | bb.plain("=" * 80) | ||
| 1247 | bb.plain("RECIPE CONFIGURATION FOR SWITCHING MODES:") | ||
| 1248 | bb.plain("=" * 80) | ||
| 1249 | bb.plain("") | ||
| 1250 | bb.plain(" Add this to your recipe to enable switching between VCS and hybrid modes:") | ||
| 1251 | bb.plain("") | ||
| 1252 | bb.plain(' # GO_MOD_FETCH_MODE: "vcs" (all git://) or "hybrid" (gomod:// + git://)') | ||
| 1253 | bb.plain(' GO_MOD_FETCH_MODE ?= "vcs"') | ||
| 1254 | bb.plain("") | ||
| 1255 | bb.plain(' # VCS mode: all modules via git://') | ||
| 1256 | bb.plain(' include ${@ "go-mod-git.inc" if d.getVar("GO_MOD_FETCH_MODE") == "vcs" else ""}') | ||
| 1257 | bb.plain(' include ${@ "go-mod-cache.inc" if d.getVar("GO_MOD_FETCH_MODE") == "vcs" else ""}') | ||
| 1258 | bb.plain("") | ||
| 1259 | bb.plain(' # Hybrid mode: gomod:// for most, git:// for selected') | ||
| 1260 | bb.plain(' include ${@ "go-mod-hybrid-gomod.inc" if d.getVar("GO_MOD_FETCH_MODE") == "hybrid" else ""}') | ||
| 1261 | bb.plain(' include ${@ "go-mod-hybrid-git.inc" if d.getVar("GO_MOD_FETCH_MODE") == "hybrid" else ""}') | ||
| 1262 | bb.plain(' include ${@ "go-mod-hybrid-cache.inc" if d.getVar("GO_MOD_FETCH_MODE") == "hybrid" else ""}') | ||
| 1263 | bb.plain("") | ||
| 1264 | bb.plain(" Then switch modes with: GO_MOD_FETCH_MODE = \"hybrid\" in local.conf") | ||
| 1265 | bb.plain("") | ||
| 1266 | } | ||
| 1267 | |||
| 1268 | addtask go_mod_recommend after do_fetch | ||
| 1269 | do_go_mod_recommend[nostamp] = "1" | ||
| 1270 | |||
| 1271 | # ============================================================================= | ||
| 1272 | # Go Module Cache Permission Fix | ||
| 1273 | # ============================================================================= | ||
| 1274 | # | ||
| 1275 | # Go's module cache creates read-only files by design. This prevents BitBake's | ||
| 1276 | # do_unpack cleanup (cleandirs = ${S}) from removing the previous build's | ||
| 1277 | # module cache, causing "Permission denied" errors. | ||
| 1278 | # | ||
| 1279 | # Solution: Add a prefunc to do_unpack that makes the module cache writable | ||
| 1280 | # before BitBake tries to clean the directory. | ||
| 1281 | # | ||
| 1282 | |||
| 1283 | python go_mod_fix_permissions() { | ||
| 1284 | """ | ||
| 1285 | Fix Go module cache permissions before do_unpack cleanup. | ||
| 1286 | |||
| 1287 | Go creates read-only files in pkg/mod to prevent accidental modification. | ||
| 1288 | BitBake's cleandirs tries to rm -rf ${S} before unpacking, which fails | ||
| 1289 | on these read-only files. This prefunc makes them writable first. | ||
| 1290 | """ | ||
| 1291 | import os | ||
| 1292 | import stat | ||
| 1293 | from pathlib import Path | ||
| 1294 | |||
| 1295 | s_dir = d.getVar('S') | ||
| 1296 | if not s_dir: | ||
| 1297 | return | ||
| 1298 | |||
| 1299 | # Check for Go module cache in various locations | ||
| 1300 | mod_paths = [ | ||
| 1301 | Path(s_dir) / 'pkg' / 'mod', | ||
| 1302 | Path(s_dir) / 'src' / 'import' / 'pkg' / 'mod', # k3s-style layout | ||
| 1303 | ] | ||
| 1304 | |||
| 1305 | for mod_cache in mod_paths: | ||
| 1306 | if mod_cache.exists(): | ||
| 1307 | bb.note(f"Fixing Go module cache permissions: {mod_cache}") | ||
| 1308 | try: | ||
| 1309 | # Walk the tree and add write permission | ||
| 1310 | for root, dirs, files in os.walk(str(mod_cache)): | ||
| 1311 | # Fix directory permissions first | ||
| 1312 | for d_name in dirs: | ||
| 1313 | d_path = os.path.join(root, d_name) | ||
| 1314 | try: | ||
| 1315 | current = os.stat(d_path).st_mode | ||
| 1316 | os.chmod(d_path, current | stat.S_IWUSR) | ||
| 1317 | except (OSError, PermissionError): | ||
| 1318 | pass | ||
| 1319 | # Fix file permissions | ||
| 1320 | for f_name in files: | ||
| 1321 | f_path = os.path.join(root, f_name) | ||
| 1322 | try: | ||
| 1323 | current = os.stat(f_path).st_mode | ||
| 1324 | os.chmod(f_path, current | stat.S_IWUSR) | ||
| 1325 | except (OSError, PermissionError): | ||
| 1326 | pass | ||
| 1327 | bb.note(f"Fixed permissions on {mod_cache}") | ||
| 1328 | except Exception as e: | ||
| 1329 | bb.warn(f"Could not fix permissions on {mod_cache}: {e}") | ||
| 1330 | |||
| 1331 | # Also check the WORKDIR for sources subdirectories | ||
| 1332 | workdir = d.getVar('WORKDIR') | ||
| 1333 | if workdir: | ||
| 1334 | sources_dir = Path(workdir) / 'sources' | ||
| 1335 | if sources_dir.exists(): | ||
| 1336 | for source_subdir in sources_dir.iterdir(): | ||
| 1337 | if source_subdir.is_dir(): | ||
| 1338 | for mod_subpath in ['pkg/mod', 'src/import/pkg/mod']: | ||
| 1339 | mod_cache = source_subdir / mod_subpath | ||
| 1340 | if mod_cache.exists(): | ||
| 1341 | bb.note(f"Fixing Go module cache permissions: {mod_cache}") | ||
| 1342 | try: | ||
| 1343 | for root, dirs, files in os.walk(str(mod_cache)): | ||
| 1344 | for d_name in dirs: | ||
| 1345 | d_path = os.path.join(root, d_name) | ||
| 1346 | try: | ||
| 1347 | current = os.stat(d_path).st_mode | ||
| 1348 | os.chmod(d_path, current | stat.S_IWUSR) | ||
| 1349 | except (OSError, PermissionError): | ||
| 1350 | pass | ||
| 1351 | for f_name in files: | ||
| 1352 | f_path = os.path.join(root, f_name) | ||
| 1353 | try: | ||
| 1354 | current = os.stat(f_path).st_mode | ||
| 1355 | os.chmod(f_path, current | stat.S_IWUSR) | ||
| 1356 | except (OSError, PermissionError): | ||
| 1357 | pass | ||
| 1358 | bb.note(f"Fixed permissions on {mod_cache}") | ||
| 1359 | except Exception as e: | ||
| 1360 | bb.warn(f"Could not fix permissions on {mod_cache}: {e}") | ||
| 1361 | } | ||
| 1362 | |||
| 1363 | # Run permission fix BEFORE do_unpack's cleandirs removes ${S} | ||
| 1364 | do_unpack[prefuncs] += "go_mod_fix_permissions" | ||
