@@ -667,6 +667,8 @@ public:
667667 " run the server" )
668668 .addSubCommand (" compile" , KJ_BIND_METHOD (*this , getCompile),
669669 " create a self-contained binary" )
670+ .addSubCommand (" measure" , KJ_BIND_METHOD (*this , getMeasure),
671+ " measure the provided worker configuration" )
670672 .addSubCommand (" test" , KJ_BIND_METHOD (*this , getTest),
671673 " run unit tests" )
672674 .build ();
@@ -679,7 +681,13 @@ public:
679681 auto builder = kj::MainBuilder (context, getVersionString (),
680682 " Serve requests based on the compiled config." ,
681683 " This binary has an embedded configuration." );
682- return addServeOptions (builder);
684+ return kj::MainBuilder (context, getVersionString (),
685+ " Runs the Workers JavaScript/Wasm runtime." )
686+ .addSubCommand (" serve" , KJ_BIND_METHOD (*this , getServeFromEmbeddedConfig),
687+ " run the server" )
688+ .addSubCommand (" measure" , KJ_BIND_METHOD (*this , getMeasure),
689+ " measure the provided worker configuration" )
690+ .build ();
683691 }
684692 }
685693
@@ -742,10 +750,19 @@ public:
742750 }
743751
744752 kj::MainFunc getServe () {
745- auto builder = kj::MainBuilder (context, getVersionString (),
753+ auto builder = getServeBuilder ();
754+ return addServeOptions (addConfigParsingOptions (builder));
755+ }
756+
757+ kj::MainFunc getServeFromEmbeddedConfig () {
758+ auto builder = getServeBuilder ();
759+ return addServeOptions (builder);
760+ }
761+
762+ kj::MainBuilder getServeBuilder () {
763+ return kj::MainBuilder (context, getVersionString (),
746764 " Serve requests based on a config." ,
747765 " Serves requests based on the configuration specified in <config-file>." );
748- return addServeOptions (addConfigParsingOptions (builder));
749766 }
750767
751768 kj::MainFunc getTest () {
@@ -806,6 +823,16 @@ public:
806823 .build ();
807824 }
808825
826+ kj::MainFunc getMeasure () {
827+ return kj::MainBuilder (context, getVersionString (),
828+ " Measures the provided worker config and outputs the hash." ,
829+ " Loads a worker's code and config in the same way as would be done by the "
830+ " `workerd.createWorker` method, hashes the full config, and returns the hash." )
831+ .expectArg (" <config-file>" , CLI_METHOD (parseWorkerConfigFile))
832+ .callAfterParsing (CLI_METHOD (measure))
833+ .build ();
834+ }
835+
809836 void addImportPath (kj::StringPtr pathStr) {
810837 auto path = fs->getCurrentPath ().evalNative (pathStr);
811838 if (fs->getRoot ().tryOpenSubdir (path) != kj::none) {
@@ -929,6 +956,19 @@ public:
929956 }
930957
931958 void parseConfigFile (kj::StringPtr pathStr) {
959+ config = parseCapnpConfig<config::Config>(pathStr);
960+ // We'll fail at getConfig() if there are multiple top level Config objects.
961+ // The error message says that you have to specify which config to use, but
962+ // it's not clear that there is any mechanism to do that.
963+ util::Autogate::initAutogate (getConfig ().getAutogates ());
964+ }
965+
966+ void parseWorkerConfigFile (kj::StringPtr pathStr) {
967+ workerConfig = parseCapnpConfig<config::Worker>(pathStr);
968+ }
969+
970+ template <typename T>
971+ kj::Maybe<typename T::Reader> parseCapnpConfig (kj::StringPtr pathStr) {
932972 if (pathStr == " -" ) {
933973 // Read from stdin.
934974
@@ -944,8 +984,8 @@ public:
944984#else
945985 auto reader = kj::heap<capnp::StreamFdMessageReader>(STDIN_FILENO, CONFIG_READER_OPTIONS);
946986#endif
947- config = reader->getRoot <config::Config>();
948987 configOwner = kj::mv (reader);
988+ return reader->getRoot <T>();
949989 } else {
950990 // Read file from disk.
951991 auto path = fs->getCurrentPath ().evalNative (pathStr);
@@ -958,11 +998,11 @@ public:
958998 mapping.size () / sizeof (capnp::word));
959999 auto reader = kj::heap<capnp::FlatArrayMessageReader>(words, CONFIG_READER_OPTIONS)
9601000 .attach (kj::mv (mapping));
961- config = reader->getRoot <config::Config>();
9621001 configOwner = kj::mv (reader);
1002+ return reader->getRoot <T>();
9631003 } else {
9641004 // Interpret as schema file.
965- schemaParser.loadCompiledTypeAndDependencies <config::Config >();
1005+ schemaParser.loadCompiledTypeAndDependencies <T >();
9661006
9671007 parsedSchema = schemaParser.parseFile (
9681008 kj::heap<SchemaFileImpl>(fs->getRoot (), fs->getCurrentPath (),
@@ -975,18 +1015,14 @@ public:
9751015 auto constSchema = nested.asConst ();
9761016 auto type = constSchema.getType ();
9771017 if (type.isStruct () &&
978- type.asStruct ().getProto ().getId () == capnp::typeId<config::Config >()) {
979- topLevelConfigConstants. add (constSchema );
1018+ type.asStruct ().getProto ().getId () == capnp::typeId<T >()) {
1019+ return constSchema. as <T>( );
9801020 }
9811021 }
9821022 }
1023+ return kj::none;
9831024 }
9841025 }
985-
986- // We'll fail at getConfig() if there are multiple top level Config objects.
987- // The error message says that you have to specify which config to use, but
988- // it's not clear that there is any mechanism to do that.
989- util::Autogate::initAutogate (getConfig ().getAutogates ());
9901026 }
9911027
9921028 void setConstName (kj::StringPtr name) {
@@ -1193,6 +1229,19 @@ public:
11931229 }
11941230 }
11951231
1232+ void measure () {
1233+ if (hadErrors) context.exit ();
1234+ auto measurement = workerd::server::measureConfig (
1235+ KJ_UNWRAP_OR (workerConfig, CLI_ERROR (" no worker config provided" )));
1236+ auto measurementHex = kj::encodeHex (measurement);
1237+ #if _WIN32
1238+ kj::FdOutputStream out (_fileno (stdout));
1239+ #else
1240+ kj::FdOutputStream out (STDOUT_FILENO);
1241+ #endif
1242+ out.write (measurementHex.asBytes ().begin (), measurementHex.size ());
1243+ }
1244+
11961245 [[noreturn]] void serve () noexcept {
11971246 serveImpl ([&](jsg::V8System& v8System, config::Config::Reader config) {
11981247#if _WIN32
@@ -1286,6 +1335,7 @@ private:
12861335
12871336 kj::Own<void > configOwner; // backing object for `config`, if it's not `schemaParser`.
12881337 kj::Maybe<config::Config::Reader> config;
1338+ kj::Maybe<config::Worker::Reader> workerConfig;
12891339
12901340 kj::Vector<int > inheritedFds;
12911341
0 commit comments