forked from lobo/lambda-zig
		
	allow packaging to be cached
This commit is contained in:
		
							parent
							
								
									a7c72d5ad2
								
							
						
					
					
						commit
						8a70f19ae5
					
				
					 1 changed files with 90 additions and 48 deletions
				
			
		|  | @ -3,7 +3,12 @@ const std = @import("std"); | ||||||
| const Package = @This(); | const Package = @This(); | ||||||
| 
 | 
 | ||||||
| step: std.Build.Step, | step: std.Build.Step, | ||||||
| lambda_zipfile: []const u8, | options: Options, | ||||||
|  | 
 | ||||||
|  | /// This is set as part of the make phase, and is the location in the cache | ||||||
|  | /// for the lambda package. The package will also be copied to the output | ||||||
|  | /// directory, but this location makes for a good cache key for deployments | ||||||
|  | zipfile_dest: ?[]const u8 = null, | ||||||
| 
 | 
 | ||||||
| const base_id: std.Build.Step.Id = .install_file; | const base_id: std.Build.Step.Id = .install_file; | ||||||
| 
 | 
 | ||||||
|  | @ -28,38 +33,9 @@ pub fn create(owner: *std.Build, options: Options) *Package { | ||||||
|             .owner = owner, |             .owner = owner, | ||||||
|             .makeFn = make, |             .makeFn = make, | ||||||
|         }), |         }), | ||||||
|         .lambda_zipfile = options.zipfile_name, |         .options = options, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     // TODO: For Windows, tar.exe can actually do zip files. tar -a -cf function.zip file1 [file2...] |  | ||||||
|     // https://superuser.com/questions/201371/create-zip-folder-from-the-command-line-windows#comment2725283_898508 |  | ||||||
|     // |  | ||||||
|     // We'll want two system commands here. One for the exe itself, and one for |  | ||||||
|     // other files (TODO: what does this latter one look like? maybe it's an option?) |  | ||||||
|     var zip_cmd = owner.addSystemCommand(&.{ "zip", "-qj9X" }); |  | ||||||
|     zip_cmd.has_side_effects = true; // TODO: move these to makeFn as we have little cache control here... |  | ||||||
|     zip_cmd.setCwd(.{ .src_path = .{ |  | ||||||
|         .owner = owner, |  | ||||||
|         .sub_path = owner.getInstallPath(.prefix, "."), |  | ||||||
|     } }); |  | ||||||
|     const zipfile = zip_cmd.addOutputFileArg(options.zipfile_name); |  | ||||||
|     zip_cmd.addArg(owner.getInstallPath(.bin, "bootstrap")); |  | ||||||
|     // std.debug.print("\nzip cmdline: {s}", .{zip}); |  | ||||||
|     if (!std.mem.eql(u8, "bootstrap", options.exe.out_filename)) { |  | ||||||
|         // We need to copy stuff around |  | ||||||
|         // TODO: should this be installing bootstrap binary in .bin directory? |  | ||||||
|         const cp_cmd = owner.addSystemCommand(&.{ "cp", owner.getInstallPath(.bin, options.exe.out_filename) }); |  | ||||||
|         cp_cmd.has_side_effects = true; |  | ||||||
|         const copy_output = cp_cmd.addOutputFileArg("bootstrap"); |  | ||||||
|         const install_copy = owner.addInstallFileWithDir(copy_output, .bin, "bootstrap"); |  | ||||||
|         cp_cmd.step.dependOn(owner.getInstallStep()); |  | ||||||
|         zip_cmd.step.dependOn(&install_copy.step); |  | ||||||
|         // might as well leave this bootstrap around for caching purposes |  | ||||||
|         // const rm_cmd = owner.addSystemCommand(&.{ "rm", owner.getInstallPath(.bin, "bootstrap"), }); |  | ||||||
|     } |  | ||||||
|     const install_zipfile = owner.addInstallFileWithDir(zipfile, .prefix, options.zipfile_name); |  | ||||||
|     install_zipfile.step.dependOn(&zip_cmd.step); |  | ||||||
|     package.step.dependOn(&install_zipfile.step); |  | ||||||
|     return package; |     return package; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -69,26 +45,92 @@ pub fn packagedFilePath(self: Package) []const u8 { | ||||||
| pub fn packagedFileLazyPath(self: Package) std.Build.LazyPath { | pub fn packagedFileLazyPath(self: Package) std.Build.LazyPath { | ||||||
|     return .{ .src_path = .{ |     return .{ .src_path = .{ | ||||||
|         .owner = self.step.owner, |         .owner = self.step.owner, | ||||||
|         .sub_path = self.step.owner.getInstallPath(.prefix, self.lambda_zipfile), |         .sub_path = self.step.owner.getInstallPath(.prefix, self.options.zipfile_name), | ||||||
|     } }; |     } }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn make(step: *std.Build.Step, node: std.Progress.Node) anyerror!void { | fn make(step: *std.Build.Step, node: std.Progress.Node) anyerror!void { | ||||||
|     // Make here doesn't actually do anything. But we want to set up this |  | ||||||
|     // step this way, so that when (if) zig stdlib gains the abiltity to write |  | ||||||
|     // zip files in addition to reading them, we can skip all the system commands |  | ||||||
|     // and just do all the things here instead |  | ||||||
|     // |  | ||||||
|     // |  | ||||||
|     // TODO: The caching plan will be: |  | ||||||
|     // |  | ||||||
|     // get a hash of the bootstrap and whatever other files we put into the zip |  | ||||||
|     // file (because a zip is not really reproducible). If the cache directory |  | ||||||
|     // has the hash as its latest hash, we have nothing to do, so we can exit |  | ||||||
|     // at that point |  | ||||||
|     // |  | ||||||
|     // Otherwise, store that hash in our cache, and copy our bootstrap, zip |  | ||||||
|     // things up and install the file into zig-out |  | ||||||
|     _ = node; |     _ = node; | ||||||
|     _ = step; |     const self: *Package = @fieldParentPtr("step", step); | ||||||
|  |     // get a hash of the bootstrap and whatever other files we put into the zip | ||||||
|  |     // file (because a zip is not really reproducible). That hash becomes the | ||||||
|  |     // cache directory, similar to the way rest of zig works | ||||||
|  |     // | ||||||
|  |     // Otherwise, create the package in our cache indexed by hash, and copy | ||||||
|  |     // our bootstrap, zip things up and install the file into zig-out | ||||||
|  |     const bootstrap = bootstrapLocation(self.*) catch |e| { | ||||||
|  |         if (@errorReturnTrace()) |trace| { | ||||||
|  |             std.debug.dumpStackTrace(trace.*); | ||||||
|  |         } | ||||||
|  |         return step.fail("Could not copy output to bootstrap: {}", .{e}); | ||||||
|  |     }; | ||||||
|  |     const bootstrap_dirname = std.fs.path.dirname(bootstrap).?; | ||||||
|  |     const zipfile_src = try std.fs.path.join(step.owner.allocator, &[_][]const u8{ bootstrap_dirname, self.options.zipfile_name }); | ||||||
|  |     self.zipfile_dest = self.step.owner.getInstallPath(.prefix, self.options.zipfile_name); | ||||||
|  |     if (std.fs.copyFileAbsolute(zipfile_src, self.zipfile_dest.?, .{})) |_| { | ||||||
|  |         // we're good here. The zip file exists in cache and has been copied | ||||||
|  |         step.result_cached = true; | ||||||
|  |     } else |_| { | ||||||
|  |         // error, but this is actually the normal case. We will zip the file | ||||||
|  |         // using system zip and store that in cache with the output file for later | ||||||
|  |         // use | ||||||
|  | 
 | ||||||
|  |         // TODO: For Windows, tar.exe can actually do zip files. | ||||||
|  |         // tar -a -cf function.zip file1 [file2...] | ||||||
|  |         // | ||||||
|  |         // See: https://superuser.com/questions/201371/create-zip-folder-from-the-command-line-windows#comment2725283_898508 | ||||||
|  |         var child = std.process.Child.init(&[_][]const u8{ | ||||||
|  |             "zip", | ||||||
|  |             "-qj9X", | ||||||
|  |             zipfile_src, | ||||||
|  |             bootstrap, | ||||||
|  |         }, self.step.owner.allocator); | ||||||
|  |         child.stdout_behavior = .Ignore; | ||||||
|  |         child.stdin_behavior = .Ignore; // we'll allow stderr through | ||||||
|  |         switch (try child.spawnAndWait()) { | ||||||
|  |             .Exited => |rc| if (rc != 0) return step.fail("Non-zero exit code {} from zip", .{rc}), | ||||||
|  |             .Signal, .Stopped, .Unknown => return step.fail("Abnormal termination from zip step", .{}), | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         try std.fs.copyFileAbsolute(zipfile_src, self.zipfile_dest.?, .{}); // It better be there now | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn bootstrapLocation(package: Package) ![]const u8 { | ||||||
|  |     const output = package.step.owner.getInstallPath(.bin, package.options.exe.out_filename); | ||||||
|  |     // We will always copy the output file, mainly because we also need the hash... | ||||||
|  |     // if (std.mem.eql(u8, "bootstrap", package.options.exe.out_filename)) | ||||||
|  |     //     return output; // easy path | ||||||
|  | 
 | ||||||
|  |     // Not so easy...read the file, get a hash of contents, see if it's in cache | ||||||
|  |     const output_file = try std.fs.openFileAbsolute(output, .{}); | ||||||
|  |     defer output_file.close(); | ||||||
|  |     const output_bytes = try output_file.readToEndAlloc(package.step.owner.allocator, 100 * 1024 * 1024); // 100MB file | ||||||
|  |     // std.Build.Cache.Hasher | ||||||
|  |     // std.Buidl.Cache.hasher_init | ||||||
|  |     var hasher = std.Build.Cache.HashHelper{}; // We'll reuse the same file hasher from cache | ||||||
|  |     hasher.addBytes(output_bytes); | ||||||
|  |     const hash = std.fmt.bytesToHex(hasher.hasher.finalResult(), .lower); | ||||||
|  |     const dest_path = try package.step.owner.cache_root.join( | ||||||
|  |         package.step.owner.allocator, | ||||||
|  |         &[_][]const u8{ "p", hash[0..], "bootstrap" }, | ||||||
|  |     ); | ||||||
|  |     const dest_file = std.fs.openFileAbsolute(dest_path, .{}) catch null; | ||||||
|  |     if (dest_file) |d| { | ||||||
|  |         d.close(); | ||||||
|  |         return dest_path; | ||||||
|  |     } | ||||||
|  |     const pkg_path = try package.step.owner.cache_root.join( | ||||||
|  |         package.step.owner.allocator, | ||||||
|  |         &[_][]const u8{"p"}, | ||||||
|  |     ); | ||||||
|  |     // Destination file does not exist. Write the bootstrap (after creating the directory) | ||||||
|  |     std.fs.makeDirAbsolute(pkg_path) catch |e| { | ||||||
|  |         std.debug.print("Could not mkdir {?s}: {}\n", .{ std.fs.path.dirname(dest_path), e }); | ||||||
|  |     }; | ||||||
|  |     std.fs.makeDirAbsolute(std.fs.path.dirname(dest_path).?) catch {}; | ||||||
|  |     const write_file = try std.fs.createFileAbsolute(dest_path, .{}); | ||||||
|  |     defer write_file.close(); | ||||||
|  |     try write_file.writeAll(output_bytes); | ||||||
|  |     return dest_path; | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue