diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in
index 3e2b7fd9da0e..950ad4fef40e 100644
--- a/Documentation/Doxyfile.in
+++ b/Documentation/Doxyfile.in
@@ -833,7 +833,9 @@ RECURSIVE              = YES
 # Note that relative paths are relative to the directory from which doxygen is
 # run.
 
-EXCLUDE                = ../src/libcamera/pipeline/
+EXCLUDE                = ../src/libcamera/device_enumerator_udev.cpp \
+			 ../src/libcamera/include/device_enumerator_udev.h \
+			 ../src/libcamera/pipeline/
 
 # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
 # directories that are symbolic links (a Unix file system feature) are excluded
diff --git a/meson.build b/meson.build
index 3434dd7c1b66..d272ff33b100 100644
--- a/meson.build
+++ b/meson.build
@@ -19,7 +19,6 @@ config_h = configuration_data()
 if cc.has_header_symbol('stdlib.h', 'secure_getenv', prefix: '#define _GNU_SOURCE')
     config_h.set('HAVE_SECURE_GETENV', 1)
 endif
-configure_file(output: 'config.h', configuration: config_h)
 
 common_arguments = [
     '-Wno-unused-parameter',
@@ -49,6 +48,8 @@ if get_option('tests')
   subdir('test')
 endif
 
+configure_file(output: 'config.h', configuration: config_h)
+
 pkg_mod = import('pkgconfig')
 pkg_mod.generate(libraries : libcamera,
                  version : '1.0',
diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
index 40a39bd2a6d9..cf881ce2e641 100644
--- a/src/libcamera/camera_manager.cpp
+++ b/src/libcamera/camera_manager.cpp
@@ -80,7 +80,7 @@ int CameraManager::start()
 		return -EBUSY;
 
 	enumerator_ = DeviceEnumerator::create();
-	if (enumerator_->enumerate())
+	if (!enumerator_ || enumerator_->enumerate())
 		return -ENODEV;
 
 	/*
diff --git a/src/libcamera/device_enumerator.cpp b/src/libcamera/device_enumerator.cpp
index 49467546bab8..f6878b3d58b3 100644
--- a/src/libcamera/device_enumerator.cpp
+++ b/src/libcamera/device_enumerator.cpp
@@ -6,14 +6,9 @@
  */
 
 #include "device_enumerator.h"
+#include "device_enumerator_udev.h"
 
-#include <fcntl.h>
-#include <libudev.h>
 #include <string.h>
-#include <sys/ioctl.h>
-#include <unistd.h>
-
-#include <libcamera/event_notifier.h>
 
 #include "log.h"
 #include "media_device.h"
@@ -148,13 +143,11 @@ std::unique_ptr<DeviceEnumerator> DeviceEnumerator::create()
 {
 	std::unique_ptr<DeviceEnumerator> enumerator;
 
-	/**
-	 * \todo Add compile time checks to only try udev enumerator if libudev
-	 * is available.
-	 */
+#ifdef HAVE_LIBUDEV
 	enumerator = utils::make_unique<DeviceEnumeratorUdev>();
 	if (!enumerator->init())
 		return enumerator;
+#endif
 
 	/*
 	 * Either udev is not available or udev initialization failed. Fall back
@@ -327,148 +320,4 @@ std::shared_ptr<MediaDevice> DeviceEnumerator::search(const DeviceMatch &dm)
  * fails
  */
 
-/**
- * \class DeviceEnumeratorUdev
- * \brief Device enumerator based on libudev
- */
-
-DeviceEnumeratorUdev::DeviceEnumeratorUdev()
-	: udev_(nullptr)
-{
-}
-
-DeviceEnumeratorUdev::~DeviceEnumeratorUdev()
-{
-	delete notifier_;
-
-	if (monitor_)
-		udev_monitor_unref(monitor_);
-	if (udev_)
-		udev_unref(udev_);
-}
-
-int DeviceEnumeratorUdev::init()
-{
-	int ret;
-
-	if (udev_)
-		return -EBUSY;
-
-	udev_ = udev_new();
-	if (!udev_)
-		return -ENODEV;
-
-	monitor_ = udev_monitor_new_from_netlink(udev_, "udev");
-	if (!monitor_)
-		return -ENODEV;
-
-	ret = udev_monitor_filter_add_match_subsystem_devtype(monitor_, "media",
-							      nullptr);
-	if (ret < 0)
-		return ret;
-
-	return 0;
-}
-
-int DeviceEnumeratorUdev::enumerate()
-{
-	struct udev_enumerate *udev_enum = nullptr;
-	struct udev_list_entry *ents, *ent;
-	int ret;
-
-	udev_enum = udev_enumerate_new(udev_);
-	if (!udev_enum)
-		return -ENOMEM;
-
-	ret = udev_enumerate_add_match_subsystem(udev_enum, "media");
-	if (ret < 0)
-		goto done;
-
-	ret = udev_enumerate_scan_devices(udev_enum);
-	if (ret < 0)
-		goto done;
-
-	ents = udev_enumerate_get_list_entry(udev_enum);
-	if (!ents)
-		goto done;
-
-	udev_list_entry_foreach(ent, ents) {
-		struct udev_device *dev;
-		const char *devnode;
-		const char *syspath = udev_list_entry_get_name(ent);
-
-		dev = udev_device_new_from_syspath(udev_, syspath);
-		if (!dev) {
-			LOG(DeviceEnumerator, Warning)
-				<< "Failed to get device for '"
-				<< syspath << "', skipping";
-			continue;
-		}
-
-		devnode = udev_device_get_devnode(dev);
-		if (!devnode) {
-			udev_device_unref(dev);
-			ret = -ENODEV;
-			goto done;
-		}
-
-		addDevice(devnode);
-
-		udev_device_unref(dev);
-	}
-done:
-	udev_enumerate_unref(udev_enum);
-	if (ret < 0)
-		return ret;
-
-	ret = udev_monitor_enable_receiving(monitor_);
-	if (ret < 0)
-		return ret;
-
-	int fd = udev_monitor_get_fd(monitor_);
-	notifier_ = new EventNotifier(fd, EventNotifier::Read);
-	notifier_->activated.connect(this, &DeviceEnumeratorUdev::udevNotify);
-
-	return 0;
-}
-
-std::string DeviceEnumeratorUdev::lookupDeviceNode(int major, int minor)
-{
-	struct udev_device *device;
-	const char *name;
-	dev_t devnum;
-	std::string deviceNode = std::string();
-
-	devnum = makedev(major, minor);
-	device = udev_device_new_from_devnum(udev_, 'c', devnum);
-	if (!device)
-		return std::string();
-
-	name = udev_device_get_devnode(device);
-	if (name)
-		deviceNode = name;
-
-	udev_device_unref(device);
-
-	return deviceNode;
-}
-
-void DeviceEnumeratorUdev::udevNotify(EventNotifier *notifier)
-{
-	struct udev_device *dev = udev_monitor_receive_device(monitor_);
-	std::string action(udev_device_get_action(dev));
-	std::string deviceNode(udev_device_get_devnode(dev));
-
-	LOG(DeviceEnumerator, Debug)
-		<< action << " device " << udev_device_get_devnode(dev);
-
-	if (action == "add") {
-		addDevice(deviceNode);
-	} else if (action == "remove") {
-		removeDevice(deviceNode);
-	}
-
-	udev_device_unref(dev);
-}
-
 } /* namespace libcamera */
diff --git a/src/libcamera/device_enumerator_udev.cpp b/src/libcamera/device_enumerator_udev.cpp
new file mode 100644
index 000000000000..cb2d21b90506
--- /dev/null
+++ b/src/libcamera/device_enumerator_udev.cpp
@@ -0,0 +1,163 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2018-2019, Google Inc.
+ *
+ * device_enumerator_udev.cpp - udev-based device enumerator
+ */
+
+#include "device_enumerator_udev.h"
+
+#include <fcntl.h>
+#include <libudev.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include <libcamera/event_notifier.h>
+
+#include "log.h"
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(DeviceEnumerator)
+
+DeviceEnumeratorUdev::DeviceEnumeratorUdev()
+	: udev_(nullptr)
+{
+}
+
+DeviceEnumeratorUdev::~DeviceEnumeratorUdev()
+{
+	delete notifier_;
+
+	if (monitor_)
+		udev_monitor_unref(monitor_);
+	if (udev_)
+		udev_unref(udev_);
+}
+
+int DeviceEnumeratorUdev::init()
+{
+	int ret;
+
+	if (udev_)
+		return -EBUSY;
+
+	udev_ = udev_new();
+	if (!udev_)
+		return -ENODEV;
+
+	monitor_ = udev_monitor_new_from_netlink(udev_, "udev");
+	if (!monitor_)
+		return -ENODEV;
+
+	ret = udev_monitor_filter_add_match_subsystem_devtype(monitor_, "media",
+							      nullptr);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+int DeviceEnumeratorUdev::enumerate()
+{
+	struct udev_enumerate *udev_enum = nullptr;
+	struct udev_list_entry *ents, *ent;
+	int ret;
+
+	udev_enum = udev_enumerate_new(udev_);
+	if (!udev_enum)
+		return -ENOMEM;
+
+	ret = udev_enumerate_add_match_subsystem(udev_enum, "media");
+	if (ret < 0)
+		goto done;
+
+	ret = udev_enumerate_scan_devices(udev_enum);
+	if (ret < 0)
+		goto done;
+
+	ents = udev_enumerate_get_list_entry(udev_enum);
+	if (!ents)
+		goto done;
+
+	udev_list_entry_foreach(ent, ents) {
+		struct udev_device *dev;
+		const char *devnode;
+		const char *syspath = udev_list_entry_get_name(ent);
+
+		dev = udev_device_new_from_syspath(udev_, syspath);
+		if (!dev) {
+			LOG(DeviceEnumerator, Warning)
+				<< "Failed to get device for '"
+				<< syspath << "', skipping";
+			continue;
+		}
+
+		devnode = udev_device_get_devnode(dev);
+		if (!devnode) {
+			udev_device_unref(dev);
+			ret = -ENODEV;
+			goto done;
+		}
+
+		addDevice(devnode);
+
+		udev_device_unref(dev);
+	}
+done:
+	udev_enumerate_unref(udev_enum);
+	if (ret < 0)
+		return ret;
+
+	ret = udev_monitor_enable_receiving(monitor_);
+	if (ret < 0)
+		return ret;
+
+	int fd = udev_monitor_get_fd(monitor_);
+	notifier_ = new EventNotifier(fd, EventNotifier::Read);
+	notifier_->activated.connect(this, &DeviceEnumeratorUdev::udevNotify);
+
+	return 0;
+}
+
+std::string DeviceEnumeratorUdev::lookupDeviceNode(int major, int minor)
+{
+	struct udev_device *device;
+	const char *name;
+	dev_t devnum;
+	std::string deviceNode = std::string();
+
+	devnum = makedev(major, minor);
+	device = udev_device_new_from_devnum(udev_, 'c', devnum);
+	if (!device)
+		return std::string();
+
+	name = udev_device_get_devnode(device);
+	if (name)
+		deviceNode = name;
+
+	udev_device_unref(device);
+
+	return deviceNode;
+}
+
+void DeviceEnumeratorUdev::udevNotify(EventNotifier *notifier)
+{
+	struct udev_device *dev = udev_monitor_receive_device(monitor_);
+	std::string action(udev_device_get_action(dev));
+	std::string deviceNode(udev_device_get_devnode(dev));
+
+	LOG(DeviceEnumerator, Debug)
+		<< action << " device " << udev_device_get_devnode(dev);
+
+	if (action == "add") {
+		addDevice(deviceNode);
+	} else if (action == "remove") {
+		removeDevice(deviceNode);
+	}
+
+	udev_device_unref(dev);
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/include/device_enumerator.h b/src/libcamera/include/device_enumerator.h
index 2801861864b8..02aec3bc50c6 100644
--- a/src/libcamera/include/device_enumerator.h
+++ b/src/libcamera/include/device_enumerator.h
@@ -7,19 +7,14 @@
 #ifndef __LIBCAMERA_DEVICE_ENUMERATOR_H__
 #define __LIBCAMERA_DEVICE_ENUMERATOR_H__
 
-#include <map>
 #include <memory>
 #include <string>
 #include <vector>
 
 #include <linux/media.h>
 
-struct udev;
-struct udev_monitor;
-
 namespace libcamera {
 
-class EventNotifier;
 class MediaDevice;
 
 class DeviceMatch
@@ -58,25 +53,6 @@ private:
 	virtual std::string lookupDeviceNode(int major, int minor) = 0;
 };
 
-class DeviceEnumeratorUdev: public DeviceEnumerator
-{
-public:
-	DeviceEnumeratorUdev();
-	~DeviceEnumeratorUdev();
-
-	int init() final;
-	int enumerate() final;
-
-private:
-	struct udev *udev_;
-	struct udev_monitor *monitor_;
-	EventNotifier *notifier_;
-
-	std::string lookupDeviceNode(int major, int minor) final;
-
-	void udevNotify(EventNotifier *notifier);
-};
-
 } /* namespace libcamera */
 
 #endif	/* __LIBCAMERA_DEVICE_ENUMERATOR_H__ */
diff --git a/src/libcamera/include/device_enumerator_udev.h b/src/libcamera/include/device_enumerator_udev.h
new file mode 100644
index 000000000000..80f9372bca36
--- /dev/null
+++ b/src/libcamera/include/device_enumerator_udev.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2018-2019, Google Inc.
+ *
+ * device_enumerator_udev.h - udev-based device enumerator
+ */
+#ifndef __LIBCAMERA_DEVICE_ENUMERATOR_UDEV_H__
+#define __LIBCAMERA_DEVICE_ENUMERATOR_UDEV_H__
+
+#include <string>
+
+#include "device_enumerator.h"
+
+struct udev;
+struct udev_monitor;
+
+namespace libcamera {
+
+class EventNotifier;
+
+class DeviceEnumeratorUdev : public DeviceEnumerator
+{
+public:
+	DeviceEnumeratorUdev();
+	~DeviceEnumeratorUdev();
+
+	int init() final;
+	int enumerate() final;
+
+private:
+	struct udev *udev_;
+	struct udev_monitor *monitor_;
+	EventNotifier *notifier_;
+
+	std::string lookupDeviceNode(int major, int minor) final;
+
+	void udevNotify(EventNotifier *notifier);
+};
+
+} /* namespace libcamera */
+
+#endif	/* __LIBCAMERA_DEVICE_ENUMERATOR_UDEV_H__ */
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index cf4edec05755..2b678237b64f 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -26,6 +26,7 @@ libcamera_sources = files([
 libcamera_headers = files([
     'include/camera_sensor.h',
     'include/device_enumerator.h',
+    'include/device_enumerator_udev.h',
     'include/event_dispatcher_poll.h',
     'include/formats.h',
     'include/log.h',
@@ -46,7 +47,14 @@ includes = [
 
 subdir('pipeline')
 
-libudev = dependency('libudev')
+libudev = dependency('libudev', required: false)
+
+if libudev.found()
+    config_h.set('HAVE_LIBUDEV', 1)
+    libcamera_sources += files([
+        'device_enumerator_udev.cpp',
+    ])
+endif
 
 libcamera = shared_library('camera',
                            libcamera_sources,
